Notations and Objects in WL

Bignya Ranjan Pathi *
Jayanta Padhikar *
* Wolfram Technical Consulting

(RowDefault[, (Spacer1), , (Spacer1), ])  #WolframTechConf

Outline

Evaluation Model

Box Language

Notations

Objects

Evaluation Model

(20/30)

Rendering/FrontEnd Typesetting

The rendering of the box expressions in the FE, and modification of the box expressions in response to the user actions.

Formatting/Kernel Typesetting

The conversion of Wolfram Language expressions to the box language for transmission to the FE.

Parsing/Kernel Untypesetting

The conversion of box expressions sent by the FE into normal expressions to be evaluated by the kernel.

Boxes in Mathematica

Boxes are 2D typesetting structures that represents all WL expressions including graphics objects, dynamic objects, control objects, etc.

To see the underlying box structure of any expression, select the cell and choose Cell ▶ Show Expression (Ctrl+Shift+E):

Simple Expressions

2+2 (* Input Cell *)

Cell[RowBox[{"2","+","2"}], "Input", ...] (* Corrsponding Cell Expression *)

1+x+x^2 (* Input Cell *)

(* Corrsponding Cell Expression *)
Cell[RowBox[{"1","+","x","+",SuperscriptBox["x","2"]}], "Input", ...]

Graphics Objects

Plot[Sin[x], {x,0,10}]

Graphics[Disk[]]

Dynamic Objects

Slider[]

Manipulate[Plot[Sin[x(1+a x)],{x,0,6}],{a,0,2}]

Box Generator

Even though the underlying structures in WL are boxes, we do not have to interact with them directly.

There is a high level interface called Box Generator that generates different types of boxes and allow us to use these powerful low-level boxes.

Styling Outputs

Style["See the Box structure", Bold, 10, FontFamily->"Helvetica"]

Mathematical Typesetting

{Subscript[a,b],Superscript[a,b],Underscript[a,b],Overscript[a,b],Subsuperscript[a,b,c],Underoverscript[a,b,c]}

Grid Layout

Grid[{{1,2}, {3,4}}]

And many more

Some Notable Boxes

There are about 100 boxes in total including RowBox, GridBox, DynamicBox etc.

Examples of Notable Boxes

SubscriptBox

DisplayForm[SubscriptBox["a","i"]]

SuperscriptBox

DisplayForm[SuperscriptBox["a","i"]]

FractionBox

DisplayForm[FractionBox[1,2]]

RowBox

DisplayForm[RowBox[{"a","+","b","+","c"}]]

GridBox

DisplayForm[GridBox[{{"a","+","b","+","c"}}]]

StyleBox

DisplayForm[StyleBox["red",FontColor->Red]]

Examples of Notable Boxes (Cont....)

InterpretationBox

Helps in creating specially formatted output for any expressions.

InterpretationBox["myVar", 3]//DisplayForm

If we copy the above output “myVar” and evaluate, it will be interpreted as 3.

myVar

ToExpression[%%, StandardForm]

The top level function which uses InterpretationBox is Interpretation

Interpretation["myVar", 3]//ToBoxes

TagBox

Used to assign tags to box structures to aid interpretation. For Example:

InverseFunction[f]

ToBoxes[InverseFunction[f],StandardForm]

Without the TagBox, this notation would be interpreted as 1/f:

(f^((-1)))

Another use case of TagBox

{MouseAppearance[f[x],"Arrow"], MouseAppearance[f[x],"LinkHand"]}

ToBoxes[MouseAppearance[f[x],"Arrow"],StandardForm]

ToBoxes[MouseAppearance[f[x],"LinkHand"],StandardForm]

Examples of Notable Boxes(Cont....)

TemplateBox

Binomial[n,k]//TraditionalForm

We can copy the above output and evaluate it to get back the original form.

(Binomial[n, k])

Two major components of TemplateBox are DisplayFunction and InterpretationFunction

DisplayFunction specifies the function used to construct boxes.

InterpretationFunction specifies the function used for evaluation.

TemplateBox[{"a","b"},"MyFunction",
InterpretationFunction:>(RowBox[{"MyFunction","[",RowBox[{#1,",",#2}],"]"}]&),
DisplayFunction:>(RowBox[{"[",RowBox[{#1,",",#2}],"]"}]&)
]//RawBoxes

If the InterpretationFunction is set to Automatic, the tag will be used as the function head of the box structure

TemplateBox[{"a","b"},"MyFunction",
InterpretationFunction:>Automatic,
DisplayFunction:>(RowBox[{"[",RowBox[{#1,",",#2}],"]"}]&)
]//RawBoxes

Tags can be used to create a pre-defined template box structure

TemplateBox[{"n", "x"},"Binomial"]//DisplayForm

CurrentValue[{StyleDefinitions,"Binomial"}]

TemplateBoxes can be nested as well

TemplateBox[{TemplateBox[{"n", "x"},"LegendreP"], "k"},"Binomial"]//RawBoxes//TraditionalForm

Dynamic TemplateBox

Create a box that displays as a popup menu and evaluates as the selected value:

TemplateBox[
<|"value"->1|>,
"popupselector",
DisplayFunction->(PopupMenuBox[Dynamic[#value],{1,2,3,4,5}]&),
InterpretationFunction->(InterpretationBox["",#value]&)
]//RawBoxes

TemplateBox along with MakeExpression and MakeBoxes can be used to define new notations.

Conversion between Expression and Box

MakeBoxes

Construct boxes to represent expression in the specified form.

Generates boxes without evaluating its input.

MakeBoxes[Plot[Sin[x],{x,1,10}]]

MakeExpression

Construct an expression corresponding to boxes.

Interprets boxes but uses (RefLink[HoldComplete , paclet:ref/HoldComplete])to prevent the resulting expression from being evaluated.

MakeExpression[%,StandardForm]

ToBoxes

Generates boxes corresponding to the output in the specified form.

Generates boxes after evaluating its input.

ToBoxes[Plot[Sin[x],{x,1,10}]]

ToExpression

Gives the expression obtained by interpreting strings or boxes as Wolfram Language input.

ToExpression[%]

Make a cell from these:

CellPrint[Cell[BoxData[ToBoxes[Manipulate[x^2,{x,0,10}]]],"Output"]]

Forms of Input and Output

CellPrint[
Cell[
BoxData[
ToBoxes[
Grid[
Map[
{Style[ToString[#], Bold, 10],#[x^2+Sqrt[y]]}&,
{StandardForm, InputForm, OutputForm, FullForm, TraditionalForm}
], 
Alignment->Left,
Frame->All,
FrameStyle->LightGray,
Background->{Automatic, {{LightPink,LightGray}}}
]
]
], "Print"
]
]

StandardForm (Ctrl + Shift + N)

In Wolfram System notebooks, expressions are by default output

Two dimensional notation

This typically requires the WL frontend.

OutputForm

Uses only ordinary keyboard characters and is the default for text-based interfaces to the Wolfram Language

InputForm (Ctrl + Shift + I)

Yields a form that can be typed directly on a keyboard

One-dimensional notation

Useful for test based interfaces (no WL frontend)

FullForm

Shows the internal form of an expression in explicit functional notation

TraditionalForm (Ctrl + Shift + T)

Uses a large collection of ad hoc rules to produce an approximation to traditional mathematical notation

TraditionalForm Vs StandardForm

TraditionalForm does not have the precision of StandardForm.

Hence, there is in general no unambiguous way to go back from a TraditionalForm representation.

ToExpression["f(6)",TraditionalForm]

ToExpression["f(6)",StandardForm]

Custom Notation

For any type of custom notations, we would like to have the following properties:

It should be evaluatable as other WL expressions

It should be nicely represented in its output format

The next few sections explain how to construct custom notations

1. Notations

2+2 -> Plus[2, 2] -> 4

Here, the operator “+” is associated with the built-in function Plus.

However, the above is not true for all operators recognized by the Wolfram Language.

The ⊕ (CirclePlus) is recognized as an infix operator, but has no predefined value.

2⊕3

2⊕3//FullForm

We can define a value for ⊕ as follows:

x_ ⊕ y_:=Mod[x+y,2]

2 ⊕ 3

x_ ⊗ y_:=(x+y)*2

2⊗3

Even though (RefLink[CirclePlus, paclet:ref/CirclePlus]) and (RefLink[CircleTimes, paclet:ref/CircleTimes]) (⊗) do not have built-in evaluation rules, they do have built-in precedences.

“Operator Input Forms” lists all the operators recognized by the Wolfram Language, in order of their precedence.

Custom Notation (Cont.....)

2. Output Formats

WL also allows us to define how expressions should be formatted for output.

Format

We can use (RefLink[Format, paclet:ref/Format])[expr] to find out whether any special rules for formatting the expression have been defined.

Format[myPlus[x_,y_]]:=x⊖y

myPlus[1,2]

Format[myPlus1[x_,y_]]:=MatrixForm[{{x}, {y}}]

myPlus1[1,2]

The downside with using Format is that the output form of your expression will not be interpreted correctly if it is given as input.

MakeBoxes and MakeExpression

myPlus/:MakeBoxes[myPlus[x_,y_,n_],StandardForm]:=RowBox[{MakeBoxes[x,StandardForm],SubscriptBox["⊕",MakeBoxes[n,StandardForm]],MakeBoxes[y,StandardForm]}]

myPlus[a,b,m+n]

However, this can not be interpreted as input

a(Subscript[⊕, m+n])b

Now, we can add the definition for MakeExpression so that the subscripted ⊕ is interpreted as a myPlus.

MakeExpression[RowBox[{x_,SubscriptBox["⊕",n_],y_}],StandardForm]:=MakeExpression[RowBox[{"myPlus","[",x,",",y,",",n,"]"}],StandardForm]

a(Subscript[⊕, m+n])b//FullForm

An important difference between MakeBoxes and Format is that the former does not evaluate its argument, so you can define rules for formatting expressions without being concerned about how these expressions would evaluate.

MakeBoxes and Interpretation

Notation for Special Functions

Special functions do not format in any specific way in the StandardForm. Consider the Gamma function:

Gamma[x]

Gamma[x]//StandardForm

It does have the specific format in the TraditionalForm:

Gamma[x]//TraditionalForm

You can make your custom notations for such special functions.

Check the box structure:

Gamma[x]//TraditionalForm//ToBoxes

CurrentValue[{StyleDefinitions,"Gamma"}]

Notation for Special Functions

Create your own boxes for representing gamma functions by following the structure above:

(* appearance *)
gammaAppearance[x_]:=
TemplateBox[{x},"myGamma",
DisplayFunction:>(RowBox[{"Γ","(",#1,")"}]&),
InterpretationFunction->(RowBox[{"Gamma","[",#1,"]"}]&),
Tooltip->"Gamma"
]

Assign the appearance function to Gamma:

Unprotect[Gamma];
Gamma/:MakeBoxes[Gamma[x_],_]:=gammaAppearance[ToBoxes[x]];
Protect[Gamma];

Now the Gamma function formats in StandardForm:

Gamma[x]

It continues to work as expected with numerical values:

Gamma[2.3]

We can copy and paste the notation from the cell above and insert our own value to get the result:

(myGamma[2.3])

Note that if we directly typeset the form using CapitalGamma, it won’t work. Since the special box structure with TemplateBox is required for the interpretation.

Γ(2.3)

Add an input alias:

AddInputAlias["gammaTypeset"->gammaAppearance["\[SelectionPlaceholder]"]]

(myGamma[5.1])

Notation Package

The Notation Package provides functionality for introducing new notations easily, intuitively, and graphically.

<<Notation`

Set boxes to be parsed as expressions

Notation[(NotationTemplateTag[x_ (Subscript[⊕, n_]) y_ ]) ⟹ (NotationTemplateTag[ circlePlus[x_,y_,n_]])]

a (Subscript[⊕, n+m]) b // FullForm

Set expressions to be formatted as boxes

Notation[(NotationTemplateTag[x_ (Subscript[⊕, n_]) y_ ]) ⟸ (NotationTemplateTag[ circlePlus[x_,y_,n_]])]

circlePlus[α, β, Γ]

Remove the notations

RemoveNotation[(NotationTemplateTag[x_ (Subscript[⊕, n_]) y_ ]) ⟹ (NotationTemplateTag[ circlePlus[x_,y_,n_]])]

RemoveNotation[(NotationTemplateTag[x_ (Subscript[⊕, n_]) y_ ]) ⟹ (NotationTemplateTag[ circlePlus[x_,y_,n_]])]

Both formatting and parsing

Notation[(NotationTemplateTag[x_ (Subscript[⊕, n_]) y_ ]) ⟺ (NotationTemplateTag[ circlePlus[x_,y_,n_]])]

a (Subscript[⊕, n+m]) b // FullForm

circlePlus[α, β, Γ]

Notation Package (Cont..)

<<Notation`

Notation[(NotationTemplateTag[ (Subscript[(({{a1_, a2_, a3_}, {b1_, b2_, b3_}})), ij]) ]) ⟺ (NotationTemplateTag[myMatrixForm[{{a1_, b1_}, {a2_, b2_}, {a3_, b3_}}]])]

myMatrixForm[{{1, 2}, {3, 4}, {5, 6}}]

(Subscript[(({{1, 3, 5}, {2, 4, 6}})), ij]) //FullForm

AddInputAlias["ij"->(NotationTemplateTag[(Subscript[(({{\[Placeholder], \[Placeholder]}, {\[Placeholder], \[Placeholder]}})), ij])])]

Notation Package (Cont..)

Symbolize

Composite boxes can be treated as a symbol.

Head[(Subscript[mh, 1])]

Symbolize[(NotationTemplateTag[(Subscript[mh, 1])])]

Head[(Subscript[mh, 1])]

InfixNotation

Composite box structure can be treated as Infix Operator.

Infix[f[a,b,c],"#"]

InfixNotation[(NotationTemplateTag[(Subscript[+, {}])]),Join]

{a,b} (Subscript[+, {}]) {c,d} (Subscript[+, {}]) {e,f}

It parses the Input and formats the output

FullForm [Hold[a (Subscript[+, {}]) b (Subscript[+, {}]) c (Subscript[+, {}]) d]]

Objects

Many of the built-in functions in WL generate objects as output. These are called elided forms.

A few examples are:

DateObject[]

TimeObject[]

TimeSeries[{2,1,6,5,7,4},{{1,2,5,10,12,15}}]

Interpolation[{1,2,3,5,8,5}]

SparseArray[{{1,1}->1,{2,2}->2,{3,3}->3,{1,3}->4}]

The advantages are:

Information/Properties are presented in a nice and compact form.

It can contain large amount of data.

It can be used as input to other function like any other WL expression.

If it is saved in a notebook, all the information and data will be saved along with the object.

Objects (Cont...)

We can access the information stored in the elided forms as well. Let us take an example of a TimeSeries object:

TimeSeries

v={2,1,6,5,7,4};
t={1,2,5,10,12,15};
ts=TimeSeries[v,{t}]

ts["Properties"]

ts["FirstDate"]

ts["LastDate"]

ts["Path"]

ts["PathLength"]

ts["Values"]

ListLinePlot[ts]

LinearModelFit

lm=LinearModelFit[{{0,1},{1,0},{3,2},{5,4}},x,x]

lm["Properties"]//Short

lm["FitResiduals"]

Objects (Cont..)

For some of the objects, we can copy the box structure and use it as an input to another function:

DateObject, TimeObject, ..

TimeSeries, TemporalData, ..

For some of the objects, we can not copy the box structure to use it as an input to another function:

Interpolation, LisInterpolation ..

SparseArray, ..

However, we can assign it a variable and then use it to access it’s properties or pass it as an input to other functions.

if = Interpolation[{1,2,3,5,8,5}]

if[2.5]

Plot[if[x],{x,1,6}]

Custom Objects

myPower

Code

Clear[myPower]
myPower[x_]:=
Module[
{mult=5,r1,r2,r3},

r1=mult*x;
r2=mult*x^2;
r3=mult*x^3;

<|"Identity"->r1,"Square"->r2,"Cube"->r3,"Properties"->{"Identity","Square","Cube"}|>
]

Usage

x=myPower[3]

x["Properties"]

x["Identity"] (* 5 * 3 *)

x["Square"] (* 5 * 3^3 *)

x["Cube"] (* 5 * 3^3 *)

Limitations

The output object itself is not nice looking. Also exposes internal information unnecessarily.

myPower[5]

Doesn’t allow for meaningful operations on the object:

x=myPower[5]+myPower[10]

Compare with Around:

Around[1,0.1]+Around[2,0.2]

Custom Object (Cont..)

myPower1

Code

Clear[myPower1];
myPower1[x_]:=myPower1[x]=
Module[
{mult=5,r1,r2,r3,appearence},

{r1,r2,r3}={mult*x,mult*x^2,mult*x^3};

power1[x]["Properties"]:={"Identity","Square","Cube"};
power1[x]["Identity"]:=r1;
power1[x]["Square"]:=r2;
power1[x]["Cube"]:=r3;

(* Formatted display *)
Format[power1[x]]:=appearence[x];
appearence[k_]:=power1[Panel[ToString[k]<>"^n"]];

(* Operations *)
power1/:power1[x1_]+power1[x2_]:=myPower1[x1+x2];

power1[x]
]

Usage

Properly formatted display:

x=myPower1[10]

Works like the previous version:

x["Properties"]
x["Identity"]
x["Square"]
x["Cube"]

y=myPower[20]

y["Square"]

z=x+y

z["Properties"]

Note z above doesn’t give 10^k + 20^k

z["Square"]

x["Square"]+y["Square"]

Custom Object (Cont..)

Formatted grid object

Code

Clear["Global`*"]
object[n_]:=
object[n]=
Module[
{graphic,makeMain,list,insertOnIndices},

graphic[i_]:=Graphics[{LightOrange,EdgeForm[Orange],Rectangle[{i+0,0},{i+1,1}],Inset[Style[HoldForm[i^2],8,Red],{i+1/2,1/2}]}];

makeMain[l_List]:=
Module[{computedList,squared},

squared=Table[With[{i=l[[i]]},HoldForm[i^2]],{i,1,Length[l]}];
computedList=If[Length[l]≥15,Partition[squared,UpTo[15]],{squared}];

Grid[computedList,Background->LightOrange,Dividers->Orange,Spacings->{2,2}]
];

insertOnIndices[l_List,pos_Integer->n1_Integer]:=l/.(pos->Sequence@@Table[pos,n1]);

list=Range[n];

myGridObject[n]["Properties"]:={"List","Display"};
myGridObject[n]["List"]:=list;
myGridObject[n]["Displayed form"]:=makeMain[list];

myGridObject/:MakeBoxes[myGridObject[n],_]:=ToBoxes[myGridObject[n]["Displayed form"]];

myGridObject/:append[myGridObject[n],atPosition[pos_]->t_]:=
Module[
{newList,newMain},

newList=insertOnIndices[myGridObject[n]["List"],pos->t];
newMain=makeMain[newList];

myGridObject[n]["List"]=newList;
myGridObject[n]["Displayed form"]=newMain
];

myGridObject/:delete[myGridObject[n],position[pos_]]:=
Module[
{newList,newMain},

newList=DeleteDuplicates[myGridObject[n]["List"],#1==#2==pos&];
newMain=makeMain[newList];

myGridObject[n]["List"]=newList;
myGridObject[n]["Displayed form"]=newMain];


myGridObject[n]
];

Usage

obj=object[20]

obj["List"]

append[obj,atPosition[5]->5]

obj["List"]

append[obj,atPosition[1]->3]

obj["List"]

delete[obj,position[5]]

obj["List"]

Summary Box

Summary Box are another type of elided form that summarize the available results and corresponding properties as a result of some evaluation.

Built-in Examples

td0=TemporalData[{2,1,6,5,7,4},{{1,2,5,10,12,15}}];

lm=LinearModelFit[{{0,1},{1,0},{3,2},{5,4}},x,x];

intp=Interpolation[{1,2,3,5,8,5}];

Extract properties/attributes

td0["Properties"]

lm["Properties"]//Short

intp["Properties"]

Use it in other function

Plot[lm[x],{x,0,5}]

DateListPlot[td0]

Construct Summary Box

Add UpValue

Similar to DownValues, UpValues are defined when a symbol does not have a meaning by itself, but acquires one when you go “upward” in the internal structure (i.e. when the symbol is an argument of another symbol):

packet/:ByteArrayToString[packet[data_,__]]:=ByteArrayToString[data]

Add DownValue

DownValues are defined when a symbol does not have a meaning by itself, but acquires one when you go “downstream” in the internal structure (i.e. when the symbol has an argument):

packet[data_List,ip_String,opts___]:=Unevaluated[packet[ByteArray[data],Lookup[{opts},"Type","TCP"],{ip,Lookup[{opts},"Port",22],DateObject[]}]]

Add functions to construct or extract properties

ipQ[ip_String]:=StringMatchQ[ip,DigitCharacter..~~"."~~DigitCharacter..~~"."~~DigitCharacter..~~"."~~DigitCharacter..]

packetQ[HoldPattern[packet][_?ByteArrayQ,type_String,{ip_?ipQ,port_?IntegerQ,_DateObject}]]:=True
packetQ[__]:=False

iProperty["Type",HoldPattern[packet[data_,type_,{ip_,port_,date_}]]]:=type
iProperty["IP",HoldPattern[packet[data_,type_,{ip_,port_,date_}]]]:=ip
iProperty["Port",HoldPattern[packet[data_,type_,{ip_,port_,date_}]]]:=port
iProperty["Data",HoldPattern[packet[data_,type_,{ip_,port_,date_}]]]:=data
iProperty["ConstructionTime",HoldPattern[packet[data_,type_,{ip_,port_,date_}]]]:=date
iProperty["Age",p_,___]:=DateDifference[p["ConstructionTime"],DateList[],"Minutes"]
(*iProperty["Properties",p_,___]:={...}*)

HoldPattern[(p_packet?packetQ)[property_String,args___]]:=iProperty[property,p,args]

Add FormatValue

packet/:MakeBoxes[p:packet[data_,type_,___],fmt_]:=Module[{alwaysGrid,sometimesGrid,icon,head},
alwaysGrid={
BoxForm`SummaryItem[{"protocole: ",p["Type"] }],
BoxForm`SummaryItem[{"address: ",p["IP"] }]
}; 

sometimesGrid={
{BoxForm`SummaryItem[{"packet length: ",Length[p["Data"]]}],BoxForm`SummaryItem[{"packet size: ",Quantity[ByteCount[p["Data"]],"Bytes"]}]},
{BoxForm`SummaryItem[{"port: ",p["Port"]}],BoxForm`SummaryItem[{"Construction Time: ",p["ConstructionTime"]}]}
};

icon=(Entity["computer network", Entity["Icon","ComputerNetwork"], "Entity[\"Icon\", \"ComputerNetwork\"]", "icon"])["Image"];
head=packet; 
BoxForm`ArrangeSummaryBox[head,p,icon,alwaysGrid,sometimesGrid,fmt]
]

Custom Summary Box

myPacket=packet[Range[100,110],"192.128.1.1","Type"->"TCP","Port"->22]

myPacket["Age"]

ByteArrayToString[myPacket]

Object Oriented Programming

Object oriented programming is another programming paradigm that can be beneficial in certain cases.

Collections of data elements treated as one unit and functions are considered part of it. The combination of both is called an Object.

The functions associated with each data objects are called methods. Usually, these methods are not defined for each object separately. Instead, they are put together in a class.

It is possible that different data types have the same characteristics or methods. These can be bundled into an abstract data type and then made available for others. This is called inheritance.

Another important feature of the OOPs is abstraction which enables to hide the actual implementation from users and expose the essential functionalities only.

Sometimes, it is desirable to have one method behave differently for different data types. This feature is called polymorphism.

Object Oriented Programming

(* Instance variables *)
accountvars = {bal};
(* Methods *)
accountmethods = {
{new, Function[init, bal = init]},
{balance, bal&},
{deposit, Function[am, bal+= am]},
{withdraw, Function[am, bal_= am]}
};
messages = First/@accountmethods;
Apply[(Switch[#1]=#2)&, accountmethods, {1}];
apply[meth_, obj_, args___]:= 
With[{lvars = variables[obj], ivars = Hold@@accountvars},
(switch[meth]/.List@@Thread[ivars:>lvars, Hold])[args]
];
(* Constructor *)
makeAccount[bal0_]:= Module[{account},
account/:variables[account] = Hold@@Unique[Evaluate[accountvars]];
account/:f_Symbol[account, args___]:= apply[f, account, args]/;MemberQ[messages, f];
new[account, bal0];
account
];

a = makeAccount[10]

deposit[a, 10]

Object Oriented Programming

Mutability

Most data structures in WL are immutable.

assoc = <|"a" -> 1, "b" -> 2|>;

assoc1 = assoc;

assoc["b"] = 18;

assoc

assoc1

Constructing Mutable Structures

New DataStructure objects

Options in Options Inspector

Some of the Graph Functionalities

Reference

http://reference.wolfram.com/language/tutorial/TextualInputAndOutput.html

https://www.google.co.in/books/edition/The_Mathematica_Programmer/tYriBQAAQBAJ?hl=en